/*
 * Sequence.h
 *
 *  Created on: Jan 23, 2013
 *      Author: Lucifer
 */

#ifndef __BFX_SEQUENCE_H__
#define __BFX_SEQUENCE_H__

#include <string.h>

namespace BFX
{

template<typename T>
class BFX_TMPL SequenceT
{
#ifdef	_MSC_VER
	static_assert(!__is_class(T), "The sequence could not support class type.");
#endif

	typedef T TYPE;
private:
	// The shared buffer data.
	struct BufferData: public REFObject
	{
		BufferData(int nCapacity) :
				m_pBuffer(NULL), m_nSize(0), m_nAllocSize(0)
		{
			BFX_REQUIRE(nCapacity >= 0);

			if (nCapacity == 0)
				return;
			Capacity(nCapacity);
		}
		BufferData(const TYPE* pSrc, int nSrcLen) :
				m_pBuffer(NULL), m_nSize(0), m_nAllocSize(0)
		{
			BFX_REQUIRE(nSrcLen >= 0);
			BFX_REQUIRE(pSrc != NULL || nSrcLen == 0);

			m_pBuffer = NULL;
			m_nSize = 0;
			m_nAllocSize = 0;

			if (nSrcLen == 0)
				return;

			Capacity(nSrcLen);
			memcpy(m_pBuffer, pSrc, nSrcLen * sizeof(TYPE));
			m_nAllocSize = nSrcLen;

		}
		virtual ~BufferData()
		{
			if (m_nAllocSize > 0)
				delete[] m_pBuffer;
		}
		void Replace(int iIndex, int nCount, const TYPE* pSrc, int nSrcLen)
		{
			BFX_REQUIRE(nSrcLen >= 0);
			BFX_REQUIRE(pSrc != NULL || nSrcLen == 0);
			BFX_REQUIRE(iIndex >= 0 && iIndex <= m_nSize);
			BFX_REQUIRE(nCount >= 0 && (iIndex + nCount) <= m_nSize);

			// ensure capacity for new size.
			int nOldSize = m_nSize;
			int nNewSize = nOldSize + nSrcLen - nCount;
			Capacity(nNewSize);

			// calculates required parameters.
			int nSrcPos = iIndex + nCount;
			int nDestPos = iIndex + nSrcLen;
			int nMoveCount = nOldSize - nSrcPos;
			if (nMoveCount > 0)
			{
				// shift existing data up/down, to make sure we have enough space to contains new data.
				memmove(m_pBuffer + nDestPos, m_pBuffer + nSrcPos, nMoveCount * sizeof(TYPE));
			}
			if (nSrcLen > 0)
			{
				memcpy(m_pBuffer + iIndex, pSrc, nSrcLen * sizeof(TYPE));
			}
			m_nSize = nNewSize;
		}
		bool IsShared() const
		{
			return (GetRefCount() > 1);
		}
		void Capacity(int nCapacity)
		{
			BFX_ASSERT(nCapacity >= 0);

			// calculate required buffer length, align by m_nAllocSize.
			int nAllocSize = __MAX(m_nAllocSize, 16);
			while (nAllocSize < nCapacity)
			{
				nAllocSize *= 2;
			}
			// reallocate new buffer if necessary.
			if (nAllocSize > m_nAllocSize)
			{
				TYPE* pNewData = (TYPE*)new BYTE[nAllocSize*sizeof(TYPE)];
				if (m_nSize > 0)
					memcpy(pNewData, m_pBuffer, m_nSize * sizeof(TYPE));

				if (m_pBuffer != NULL)
					delete[] (BYTE*)m_pBuffer;

				m_pBuffer = pNewData;
				m_nAllocSize = nAllocSize;
			}
		}

		TYPE* m_pBuffer;
		int m_nSize;
		int m_nAllocSize;
	};
public:
	//
	// Construction
	//

	// Initializes the buffer with given capacity.
	SequenceT(int nCapacity = 0) :
			m_pData(new BufferData(nCapacity))
	{
	}
	// Initializes the buffer with given data and length.
	SequenceT(const TYPE* pSrc, int nSrcLen) :
			m_pData(new BufferData(pSrc, nSrcLen))
	{
	}
	// The copy constructor
	SequenceT(const SequenceT& src) :
			m_pData(src.m_pData)
	{
	}
	virtual ~SequenceT()
	{
	}

	//
	// Operations
	//
	int GetSize() const
	{
		return m_pData->m_nSize;
	}
	// Returns the pointer to gain direct access to the elements in the buffer.
	const TYPE* GetData() const
	{
		return m_pData->m_pBuffer;
	}
	// Assignment
	SequenceT& operator=(const SequenceT& src)
	{
		m_pData = src.m_pData;
		return (*this);
	}
	// Sets or gets the element at the specified index.
	const TYPE& operator[](int iIndex) const
	{
		BFX_REQUIRE(GetSize() > iIndex);

		return GetData()[iIndex];
	}
	TYPE& operator[](int iIndex)
	{
		int nSize = GetSize();
		BFX_REQUIRE(nSize > iIndex);

		return GetBuffer(nSize)[iIndex];
	}

	// Appends new elements to the end of the buffer.
	void Append(const TYPE* pSrc, int nSrcLen)
	{
		Insert(GetSize(), pSrc, nSrcLen);
	}
	void Append(const TYPE& src)
	{
		Append(&src, 1);
	}
	// Appends another buffer to the end of the current buffer.
	void Append(const SequenceT& src)
	{
		Insert(GetSize(), src);
	}
	// Inserts new elements to the given index of the buffer.
	void Insert(int iIndex, const TYPE* pSrc, int nSrcLen)
	{
		Replace(iIndex, 0, pSrc, nSrcLen);
	}
	void Insert(int iIndex, const TYPE& src)
	{
		Insert(iIndex, &src, 1);
	}
	// Inserts another buffer to the given index of the current buffer.
	void Insert(int iIndex, const SequenceT& src)
	{
		Replace(iIndex, 0, src.GetData(), src.GetSize());
	}
	// Removes elements at index.
	void Remove(int iIndex, int nCount = 1)
	{
		Replace(iIndex, nCount, NULL, 0);
	}
	// Replaces elements at index by given new data.
	void Replace(int iIndex, int nCount, const TYPE* pSrc, int nSrcLen)
	{
		if (m_pData->IsShared())
		{
			// clone it
			m_pData = new BufferData(m_pData->m_pBuffer, m_pData->m_nSize);
		}
		m_pData->Replace(iIndex, nCount, pSrc, nSrcLen);
	}
	void Replace(int iIndex, const TYPE& src)
	{
		Replace(iIndex, 1, &src, 1);
	}
	// Replaces elements at index by another buffer.
	void Replace(int iIndex, int nCount, const SequenceT& src)
	{
		Replace(iIndex, nCount, src.GetData(), src.GetSize());
	}

	TYPE* GetBuffer(int nLength)
	{
		if (m_pData->IsShared())
		{
			// clone it
			REF<BufferData> pOldData = m_pData;
			int nAllocLength = __MAX(pOldData->m_nAllocSize, nLength);
			m_pData = new BufferData(nAllocLength);
			// insertion
			m_pData->Replace(0, 0, pOldData->m_pBuffer, pOldData->m_nSize);
		}
		else
		{
			m_pData->Capacity(nLength);
		}

		return m_pData->m_pBuffer;
	}
	void ReleaseBuffer(int nLength)
	{
		BFX_ASSERT(nLength >= 0);
		BFX_ASSERT(nLength <= m_pData->m_nAllocSize);
		BFX_ASSERT(!m_pData->IsShared());

		m_pData->m_nSize = nLength;
	}
private:
	REF<BufferData> m_pData;
};

} /* namespace BFX */

#endif /* __BFX_SEQUENCE_H__ */
